#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/resource.h>

#include "../comm/log.hpp"
#include "../comm/util.hpp"

/***********************
 * 该模块负责运行程序并存放输出结果，不进行判断对错
 ***********************/

namespace ns_runner
{
	using namespace ns_log;
	using namespace ns_util;
	class Runner
	{
		public:
		Runner(){}
		~Runner(){}
		public:
		//提供设置进程占用资源大小的接口
		static void SetProcLimit(int cpu_limit,int mem_limit)
		{
			//设置CPU时长
			struct rlimit cpu_rlimit;
			cpu_rlimit.rlim_cur = cpu_limit;
			cpu_rlimit.rlim_max = RLIM_INFINITY;
			setrlimit(RLIMIT_CPU,&cpu_rlimit);
			//设置内存大小
			struct rlimit mem_rlimit;
			mem_rlimit.rlim_cur = mem_limit * 1024;	//转化为kb
			mem_rlimit.rlim_max = RLIM_INFINITY;
			setrlimit(RLIMIT_AS,&mem_rlimit);
		}

		// 指明文件名即可，不需要代理路径，不需要带后缀
		/*******************************************
		* 返回值 > 0: 程序异常了，退出时收到了信号，返回值就是对应的信号编号
		* 返回值 == 0: 正常运行完毕的，结果保存到了对应的临时文件中
		* 返回值 < 0: 内部错误
		*
		* cpu_limit: 该程序运行的时候，可以使用的最大cpu资源上限
		* mem_limit: 改程序运行的时候，可以使用的最大的内存大小(KB)
		* *****************************************/
		static int Run(const std::string& file_name,int cpu_limit,int mem_limit)
		{
			/*********************************************
			* 程序运行：
			* 1. 代码跑完，结果正确
			* 2. 代码跑完，结果不正确
			* 3. 代码没跑完，异常了
			* Run需要考虑代码跑完，结果正确与否吗？？不考虑！
			* 结果正确与否：是由我们的测试用例决定的！
			* 我们只考虑：是否正确运行完毕
			*
			* 我们必须知道可执行程序是谁？
			* 一个程序在默认启动的时候
			* 标准输入: 不处理
			* 标准输出: 程序运行完成，输出结果是什么
			* 标准错误: 运行时错误信息
			* *******************************************/

			std::string _execute = PathUtil::Exe(file_name);
			std::string _stdin = PathUtil::Stdin(file_name);
			std::string _stdout = PathUtil::Stdout(file_name);
			std::string _stderr = PathUtil::Stderr(file_name);

			umask(0);
			int _stdin_fd = open(_stdin.c_str(),O_CREAT | O_RDONLY,0644);
			int _stdout_fd = open(_stdout.c_str(),O_CREAT | O_WRONLY,0644);
			int _stderr_fd = open(_stderr.c_str(),O_CREAT | O_WRONLY,0644);
			if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
			{
				LOG(ERROR) <<"运行时打开标准文件失败" << "\n";
				return -1;		//代表文件打开失败
			}

			pid_t pid = fork();
			if(pid < 0)
			{
				LOG(ERROR) << "运行时创建子进程失败" << "\n";
				::close(_stdin_fd);
				::close(_stdout_fd);
				::close(_stderr_fd);
				return -2;	//代表创建进程失败
			}
			else if(pid == 0)	// 进行进程替换去运行编译好的程序
			{
				//子进程
				dup2(_stdin_fd,0);
				dup2(_stdout_fd,1);
				dup2(_stderr_fd,2);
				
				SetProcLimit(cpu_limit,mem_limit);
				execl(_execute.c_str(),_execute.c_str(),nullptr);
				exit(1);
			}
			else
			{
				//父进程
				close(_stdin_fd);
				close(_stdout_fd);
				close(_stderr_fd);
				int status = 0;
				waitpid(pid,&status,0);
				//字进程替换程序运行。如果运行异常结束一定会收到信号
				LOG(INFO) << "运行完毕, info: " << (status & 0x7F) << std::endl;
				return status & 0x7f;
			}
		}
	};
}